{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "88b4974c-6437-422d-afae-daa2884ad633",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from transformers import CLIPModel, CLIPTextModel, CLIPTokenizer\n",
    "from diffusers import AutoencoderKL, UNet2DConditionModel\n",
    "\n",
    "#NOTE: Last tested working diffusers version is diffusers==0.4.1, https://github.com/huggingface/diffusers/releases/tag/v0.4.1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38ebfbd7-5026-4830-93e5-d43272db8912",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Init CLIP tokenizer and model\n",
    "model_path_clip = \"openai/clip-vit-large-patch14\"\n",
    "clip_tokenizer = CLIPTokenizer.from_pretrained(model_path_clip)\n",
    "clip_model = CLIPModel.from_pretrained(model_path_clip, torch_dtype=torch.float16)\n",
    "clip = clip_model.text_model\n",
    "\n",
    "#Init diffusion model\n",
    "auth_token = True #Replace this with huggingface auth token as a string if model is not already downloaded\n",
    "model_path_diffusion = \"CompVis/stable-diffusion-v1-4\"\n",
    "unet = UNet2DConditionModel.from_pretrained(model_path_diffusion, subfolder=\"unet\", use_auth_token=auth_token, revision=\"fp16\", torch_dtype=torch.float16)\n",
    "vae = AutoencoderKL.from_pretrained(model_path_diffusion, subfolder=\"vae\", use_auth_token=auth_token, revision=\"fp16\", torch_dtype=torch.float16)\n",
    "\n",
    "#Move to GPU\n",
    "device = \"cuda\"\n",
    "unet.to(device)\n",
    "vae.to(device)\n",
    "clip.to(device)\n",
    "print(\"Loaded all models\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e6aa4473-05c4-4d35-82b4-23a01f004cfc",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import random\n",
    "from PIL import Image\n",
    "from diffusers import LMSDiscreteScheduler, DDIMScheduler\n",
    "from tqdm.auto import tqdm\n",
    "from torch import autocast\n",
    "from difflib import SequenceMatcher\n",
    "\n",
    "def init_attention_weights(weight_tuples):\n",
    "    tokens_length = clip_tokenizer.model_max_length\n",
    "    weights = torch.ones(tokens_length)\n",
    "    \n",
    "    for i, w in weight_tuples:\n",
    "        if i < tokens_length and i >= 0:\n",
    "            weights[i] = w\n",
    "    \n",
    "    \n",
    "    for name, module in unet.named_modules():\n",
    "        module_name = type(module).__name__\n",
    "        if module_name == \"CrossAttention\" and \"attn2\" in name:\n",
    "            module.last_attn_slice_weights = weights.to(device)\n",
    "        if module_name == \"CrossAttention\" and \"attn1\" in name:\n",
    "            module.last_attn_slice_weights = None\n",
    "    \n",
    "\n",
    "def init_attention_edit(tokens, tokens_edit):\n",
    "    tokens_length = clip_tokenizer.model_max_length\n",
    "    mask = torch.zeros(tokens_length)\n",
    "    indices_target = torch.arange(tokens_length, dtype=torch.long)\n",
    "    indices = torch.zeros(tokens_length, dtype=torch.long)\n",
    "\n",
    "    tokens = tokens.input_ids.numpy()[0]\n",
    "    tokens_edit = tokens_edit.input_ids.numpy()[0]\n",
    "    \n",
    "    for name, a0, a1, b0, b1 in SequenceMatcher(None, tokens, tokens_edit).get_opcodes():\n",
    "        if b0 < tokens_length:\n",
    "            if name == \"equal\" or (name == \"replace\" and a1-a0 == b1-b0):\n",
    "                mask[b0:b1] = 1\n",
    "                indices[b0:b1] = indices_target[a0:a1]\n",
    "\n",
    "    for name, module in unet.named_modules():\n",
    "        module_name = type(module).__name__\n",
    "        if module_name == \"CrossAttention\" and \"attn2\" in name:\n",
    "            module.last_attn_slice_mask = mask.to(device)\n",
    "            module.last_attn_slice_indices = indices.to(device)\n",
    "        if module_name == \"CrossAttention\" and \"attn1\" in name:\n",
    "            module.last_attn_slice_mask = None\n",
    "            module.last_attn_slice_indices = None\n",
    "\n",
    "\n",
    "def init_attention_func():\n",
    "    #ORIGINAL SOURCE CODE: https://github.com/huggingface/diffusers/blob/91ddd2a25b848df0fa1262d4f1cd98c7ccb87750/src/diffusers/models/attention.py#L276\n",
    "    def new_attention(self, query, key, value):\n",
    "        # TODO: use baddbmm for better performance\n",
    "        attention_scores = torch.matmul(query, key.transpose(-1, -2)) * self.scale\n",
    "        attn_slice = attention_scores.softmax(dim=-1)\n",
    "        # compute attention output\n",
    "        \n",
    "        if self.use_last_attn_slice:\n",
    "            if self.last_attn_slice_mask is not None:\n",
    "                new_attn_slice = torch.index_select(self.last_attn_slice, -1, self.last_attn_slice_indices)\n",
    "                attn_slice = attn_slice * (1 - self.last_attn_slice_mask) + new_attn_slice * self.last_attn_slice_mask\n",
    "            else:\n",
    "                attn_slice = self.last_attn_slice\n",
    "\n",
    "            self.use_last_attn_slice = False\n",
    "\n",
    "        if self.save_last_attn_slice:\n",
    "            self.last_attn_slice = attn_slice\n",
    "            self.save_last_attn_slice = False\n",
    "\n",
    "        if self.use_last_attn_weights and self.last_attn_slice_weights is not None:\n",
    "            attn_slice = attn_slice * self.last_attn_slice_weights\n",
    "            self.use_last_attn_weights = False\n",
    "        \n",
    "        hidden_states = torch.matmul(attn_slice, value)\n",
    "        # reshape hidden_states\n",
    "        hidden_states = self.reshape_batch_dim_to_heads(hidden_states)\n",
    "        return hidden_states\n",
    "    \n",
    "    def new_sliced_attention(self, query, key, value, sequence_length, dim):\n",
    "        \n",
    "        batch_size_attention = query.shape[0]\n",
    "        hidden_states = torch.zeros(\n",
    "            (batch_size_attention, sequence_length, dim // self.heads), device=query.device, dtype=query.dtype\n",
    "        )\n",
    "        slice_size = self._slice_size if self._slice_size is not None else hidden_states.shape[0]\n",
    "        for i in range(hidden_states.shape[0] // slice_size):\n",
    "            start_idx = i * slice_size\n",
    "            end_idx = (i + 1) * slice_size\n",
    "            attn_slice = (\n",
    "                torch.matmul(query[start_idx:end_idx], key[start_idx:end_idx].transpose(1, 2)) * self.scale\n",
    "            )  # TODO: use baddbmm for better performance\n",
    "            attn_slice = attn_slice.softmax(dim=-1)\n",
    "            \n",
    "            if self.use_last_attn_slice:\n",
    "                if self.last_attn_slice_mask is not None:\n",
    "                    new_attn_slice = torch.index_select(self.last_attn_slice, -1, self.last_attn_slice_indices)\n",
    "                    attn_slice = attn_slice * (1 - self.last_attn_slice_mask) + new_attn_slice * self.last_attn_slice_mask\n",
    "                else:\n",
    "                    attn_slice = self.last_attn_slice\n",
    "                \n",
    "                self.use_last_attn_slice = False\n",
    "                    \n",
    "            if self.save_last_attn_slice:\n",
    "                self.last_attn_slice = attn_slice\n",
    "                self.save_last_attn_slice = False\n",
    "                \n",
    "            if self.use_last_attn_weights and self.last_attn_slice_weights is not None:\n",
    "                attn_slice = attn_slice * self.last_attn_slice_weights\n",
    "                self.use_last_attn_weights = False\n",
    "            \n",
    "            attn_slice = torch.matmul(attn_slice, value[start_idx:end_idx])\n",
    "\n",
    "            hidden_states[start_idx:end_idx] = attn_slice\n",
    "\n",
    "        # reshape hidden_states\n",
    "        hidden_states = self.reshape_batch_dim_to_heads(hidden_states)\n",
    "        return hidden_states\n",
    "\n",
    "    for name, module in unet.named_modules():\n",
    "        module_name = type(module).__name__\n",
    "        if module_name == \"CrossAttention\":\n",
    "            module.last_attn_slice = None\n",
    "            module.use_last_attn_slice = False\n",
    "            module.use_last_attn_weights = False\n",
    "            module.save_last_attn_slice = False\n",
    "            module._sliced_attention = new_sliced_attention.__get__(module, type(module))\n",
    "            module._attention = new_attention.__get__(module, type(module))\n",
    "            \n",
    "def use_last_tokens_attention(use=True):\n",
    "    for name, module in unet.named_modules():\n",
    "        module_name = type(module).__name__\n",
    "        if module_name == \"CrossAttention\" and \"attn2\" in name:\n",
    "            module.use_last_attn_slice = use\n",
    "            \n",
    "def use_last_tokens_attention_weights(use=True):\n",
    "    for name, module in unet.named_modules():\n",
    "        module_name = type(module).__name__\n",
    "        if module_name == \"CrossAttention\" and \"attn2\" in name:\n",
    "            module.use_last_attn_weights = use\n",
    "            \n",
    "def use_last_self_attention(use=True):\n",
    "    for name, module in unet.named_modules():\n",
    "        module_name = type(module).__name__\n",
    "        if module_name == \"CrossAttention\" and \"attn1\" in name:\n",
    "            module.use_last_attn_slice = use\n",
    "            \n",
    "def save_last_tokens_attention(save=True):\n",
    "    for name, module in unet.named_modules():\n",
    "        module_name = type(module).__name__\n",
    "        if module_name == \"CrossAttention\" and \"attn2\" in name:\n",
    "            module.save_last_attn_slice = save\n",
    "            \n",
    "def save_last_self_attention(save=True):\n",
    "    for name, module in unet.named_modules():\n",
    "        module_name = type(module).__name__\n",
    "        if module_name == \"CrossAttention\" and \"attn1\" in name:\n",
    "            module.save_last_attn_slice = save\n",
    "            \n",
    "@torch.no_grad()\n",
    "def stablediffusion(prompt=\"\", prompt_edit=None, prompt_edit_token_weights=[], init_latents=None, prompt_edit_tokens_start=0.0, prompt_edit_tokens_end=1.0, prompt_edit_spatial_start=0.0, prompt_edit_spatial_end=1.0, guidance_scale=7.5, steps=50, seed=None, width=512, height=512):\n",
    "    #Change size to multiple of 64 to prevent size mismatches inside model\n",
    "    if init_latents is not None:\n",
    "        width = init_latents.shape[-1] * 8\n",
    "        height = init_latents.shape[-2] * 8\n",
    "        \n",
    "    width = width - width % 64\n",
    "    height = height - height % 64\n",
    "    \n",
    "    #If seed is None, randomly select seed from 0 to 2^32-1\n",
    "    if seed is None: seed = random.randrange(2**32 - 1)\n",
    "    generator = torch.cuda.manual_seed(seed)\n",
    "    \n",
    "    #Set inference timesteps to scheduler\n",
    "    scheduler = LMSDiscreteScheduler(beta_start=0.00085, beta_end=0.012, beta_schedule=\"scaled_linear\", num_train_timesteps=1000)\n",
    "    scheduler.set_timesteps(steps)\n",
    "    \n",
    "    init_latent = torch.zeros((1, unet.in_channels, height // 8, width // 8), device=device)\n",
    "    t_start = 0\n",
    "    \n",
    "    #Generate random normal noise\n",
    "    noise = torch.randn(init_latent.shape, generator=generator, device=device)\n",
    "    \n",
    "    #If init_latents is used, initialize noise as init_latent\n",
    "    if init_latents is not None:\n",
    "        noise = init_latents\n",
    "    \n",
    "    init_latents = noise\n",
    "    latent = scheduler.add_noise(init_latent, noise, torch.tensor([scheduler.timesteps[t_start]], device=device)).to(device)\n",
    "        \n",
    "    #Process clip\n",
    "    with autocast(device):\n",
    "        tokens_unconditional = clip_tokenizer(\"\", padding=\"max_length\", max_length=clip_tokenizer.model_max_length, truncation=True, return_tensors=\"pt\", return_overflowing_tokens=True)\n",
    "        embedding_unconditional = clip(tokens_unconditional.input_ids.to(device)).last_hidden_state\n",
    "\n",
    "        tokens_conditional = clip_tokenizer(prompt, padding=\"max_length\", max_length=clip_tokenizer.model_max_length, truncation=True, return_tensors=\"pt\", return_overflowing_tokens=True)\n",
    "        embedding_conditional = clip(tokens_conditional.input_ids.to(device)).last_hidden_state\n",
    "\n",
    "        #Process prompt editing\n",
    "        if prompt_edit is not None:\n",
    "            tokens_conditional_edit = clip_tokenizer(prompt_edit, padding=\"max_length\", max_length=clip_tokenizer.model_max_length, truncation=True, return_tensors=\"pt\", return_overflowing_tokens=True)\n",
    "            embedding_conditional_edit = clip(tokens_conditional_edit.input_ids.to(device)).last_hidden_state\n",
    "            \n",
    "            init_attention_edit(tokens_conditional, tokens_conditional_edit)\n",
    "            \n",
    "        init_attention_func()\n",
    "        init_attention_weights(prompt_edit_token_weights)\n",
    "            \n",
    "        timesteps = scheduler.timesteps[t_start:]\n",
    "        \n",
    "        for i, t in tqdm(enumerate(timesteps), total=len(timesteps)):\n",
    "            t_index = t_start + i\n",
    "\n",
    "            #sigma = scheduler.sigmas[t_index]\n",
    "            latent_model_input = latent\n",
    "            latent_model_input = scheduler.scale_model_input(latent_model_input, t)\n",
    "            \n",
    "            #Predict the unconditional noise residual\n",
    "            noise_pred_uncond = unet(latent_model_input, t, encoder_hidden_states=embedding_unconditional).sample\n",
    "            \n",
    "            #Prepare the Cross-Attention layers\n",
    "            if prompt_edit is not None:\n",
    "                save_last_tokens_attention()\n",
    "                save_last_self_attention()\n",
    "            else:\n",
    "                #Use weights on non-edited prompt when edit is None\n",
    "                use_last_tokens_attention_weights()\n",
    "                \n",
    "            #Predict the conditional noise residual and save the cross-attention layer activations\n",
    "            noise_pred_cond = unet(latent_model_input, t, encoder_hidden_states=embedding_conditional).sample\n",
    "            \n",
    "            #Edit the Cross-Attention layer activations\n",
    "            if prompt_edit is not None:\n",
    "                t_scale = t / scheduler.num_train_timesteps\n",
    "                if t_scale >= prompt_edit_tokens_start and t_scale <= prompt_edit_tokens_end:\n",
    "                    use_last_tokens_attention()\n",
    "                if t_scale >= prompt_edit_spatial_start and t_scale <= prompt_edit_spatial_end:\n",
    "                    use_last_self_attention()\n",
    "                    \n",
    "                #Use weights on edited prompt\n",
    "                use_last_tokens_attention_weights()\n",
    "\n",
    "                #Predict the edited conditional noise residual using the cross-attention masks\n",
    "                noise_pred_cond = unet(latent_model_input, t, encoder_hidden_states=embedding_conditional_edit).sample\n",
    "                \n",
    "            #Perform guidance\n",
    "            noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_cond - noise_pred_uncond)\n",
    "            \n",
    "            latent = scheduler.step(noise_pred, t_index, latent).prev_sample\n",
    "\n",
    "        #scale and decode the image latents with vae\n",
    "        latent = latent / 0.18215\n",
    "        image = vae.decode(latent.to(vae.dtype)).sample\n",
    "\n",
    "    image = (image / 2 + 0.5).clamp(0, 1)\n",
    "    image = image.cpu().permute(0, 2, 3, 1).numpy()\n",
    "    image = (image[0] * 255).round().astype(\"uint8\")\n",
    "    return Image.fromarray(image)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ab397f07-f069-4e5d-96fe-6edcc779884f",
   "metadata": {},
   "outputs": [],
   "source": [
    "@torch.no_grad()\n",
    "def inversestablediffusion(init_image, prompt=\"\", guidance_scale=3.0, steps=50, refine_iterations=3, refine_strength=0.9, refine_skip=0.7):\n",
    "    #Change size to multiple of 64 to prevent size mismatches inside model\n",
    "    width, height = init_image.size\n",
    "    width = width - width % 64\n",
    "    height = height - height % 64\n",
    "    \n",
    "    image_width, image_height = init_image.size\n",
    "    left = (image_width - width)/2\n",
    "    top = (image_height - height)/2\n",
    "    right = left + width\n",
    "    bottom = top + height\n",
    "    \n",
    "    init_image = init_image.crop((left, top, right, bottom))\n",
    "    init_image = np.array(init_image).astype(np.float32) / 255.0 * 2.0 - 1.0\n",
    "    init_image = torch.from_numpy(init_image[np.newaxis, ...].transpose(0, 3, 1, 2))\n",
    "\n",
    "    #If there is alpha channel, composite alpha for white, as the diffusion model does not support alpha channel\n",
    "    if init_image.shape[1] > 3:\n",
    "        init_image = init_image[:, :3] * init_image[:, 3:] + (1 - init_image[:, 3:])\n",
    "\n",
    "    #Move image to GPU\n",
    "    init_image = init_image.to(device)\n",
    "\n",
    "    train_steps = 1000\n",
    "    step_ratio = train_steps // steps\n",
    "    timesteps = torch.from_numpy(np.linspace(0, train_steps - 1, steps + 1, dtype=float)).int().to(device)\n",
    "    \n",
    "    betas = torch.linspace(0.00085**0.5, 0.012**0.5, train_steps, dtype=torch.float32) ** 2\n",
    "    alphas = torch.cumprod(1 - betas, dim=0)\n",
    "    \n",
    "    init_step = 0\n",
    "    \n",
    "    #Fixed seed such that the vae sampling is deterministic, shouldn't need to be changed by the user...\n",
    "    generator = torch.cuda.manual_seed(798122)\n",
    "    \n",
    "    #Process clip\n",
    "    with autocast(device):\n",
    "        init_latent = vae.encode(init_image).latent_dist.sample(generator=generator) * 0.18215\n",
    "        \n",
    "        tokens_unconditional = clip_tokenizer(\"\", padding=\"max_length\", max_length=clip_tokenizer.model_max_length, truncation=True, return_tensors=\"pt\", return_overflowing_tokens=True)\n",
    "        embedding_unconditional = clip(tokens_unconditional.input_ids.to(device)).last_hidden_state\n",
    "\n",
    "        tokens_conditional = clip_tokenizer(prompt, padding=\"max_length\", max_length=clip_tokenizer.model_max_length, truncation=True, return_tensors=\"pt\", return_overflowing_tokens=True)\n",
    "        embedding_conditional = clip(tokens_conditional.input_ids.to(device)).last_hidden_state\n",
    "        \n",
    "        latent = init_latent\n",
    "\n",
    "        for i in tqdm(range(steps), total=steps):\n",
    "            t_index = i + init_step\n",
    "            \n",
    "            t = timesteps[t_index]\n",
    "            t1 = timesteps[t_index + 1]\n",
    "            #Magic number for tless taken from Narnia, used for backwards CFG correction\n",
    "            tless = t - (t1 - t) * 0.25\n",
    "            \n",
    "            ap = alphas[t] ** 0.5\n",
    "            bp = (1 - alphas[t]) ** 0.5\n",
    "            ap1 = alphas[t1] ** 0.5\n",
    "            bp1 = (1 - alphas[t1]) ** 0.5\n",
    "            \n",
    "            latent_model_input = latent\n",
    "            #Predict the unconditional noise residual\n",
    "            noise_pred_uncond = unet(latent_model_input, t, encoder_hidden_states=embedding_unconditional).sample\n",
    "            \n",
    "            #Predict the conditional noise residual and save the cross-attention layer activations\n",
    "            noise_pred_cond = unet(latent_model_input, t, encoder_hidden_states=embedding_conditional).sample\n",
    "            \n",
    "            #Perform guidance\n",
    "            noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_cond - noise_pred_uncond)\n",
    "            \n",
    "            #One reverse DDIM step\n",
    "            px0 = (latent_model_input - bp * noise_pred) / ap\n",
    "            latent = ap1 * px0 + bp1 * noise_pred\n",
    "            \n",
    "            #Initialize loop variables\n",
    "            latent_refine = latent\n",
    "            latent_orig = latent_model_input\n",
    "            min_error = 1e10\n",
    "            lr = refine_strength\n",
    "            \n",
    "            #Finite difference gradient descent method to correct for classifier free guidance, performs best when CFG is high\n",
    "            #Very slow and unoptimized, might be able to use Newton's method or some other multidimensional root finding method\n",
    "            if i > (steps * refine_skip):\n",
    "                for k in range(refine_iterations):\n",
    "                    #Compute reverse diffusion process to get better prediction for noise at t+1\n",
    "                    #tless and t are used instead of the \"numerically correct\" t+1, produces way better results in practice, reason unknown...\n",
    "                    noise_pred_uncond = unet(latent_refine, tless, encoder_hidden_states=embedding_unconditional).sample\n",
    "                    noise_pred_cond = unet(latent_refine, t, encoder_hidden_states=embedding_conditional).sample\n",
    "                    noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_cond - noise_pred_uncond)\n",
    "                    \n",
    "                    #One forward DDIM Step\n",
    "                    px0 = (latent_refine - bp1 * noise_pred) / ap1\n",
    "                    latent_refine_orig = ap * px0 + bp * noise_pred\n",
    "                    \n",
    "                    #Save latent if error is smaller\n",
    "                    error = float((latent_orig - latent_refine_orig).abs_().sum())\n",
    "                    if error < min_error:\n",
    "                        latent = latent_refine\n",
    "                        min_error = error\n",
    "\n",
    "                    #print(k, error)\n",
    "                    \n",
    "                    #Break to avoid \"overfitting\", too low error does not produce good results in practice, why?\n",
    "                    if min_error < 5:\n",
    "                        break\n",
    "                    \n",
    "                    #\"Learning rate\" decay if error decrease is too small or negative (dampens oscillations)\n",
    "                    if (min_error - error) < 1:\n",
    "                        lr *= 0.9\n",
    "                    \n",
    "                    #Finite difference gradient descent\n",
    "                    latent_refine = latent_refine + (latent_model_input - latent_refine_orig) * lr\n",
    "                    \n",
    "            \n",
    "    return latent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f2e9bb8d-2050-4283-89b1-7ffe18a8475f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def prompt_token(prompt, index):\n",
    "    tokens = clip_tokenizer(prompt, padding=\"max_length\", max_length=clip_tokenizer.model_max_length, truncation=True, return_tensors=\"pt\", return_overflowing_tokens=True).input_ids[0]\n",
    "    return clip_tokenizer.decode(tokens[index:index+1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38221201-23c7-4760-bfb2-6aae227316b3",
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt_token(\"a photo of a woman with blonde hair\", 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "05b63c1f-9bb6-4575-87ab-05663b8846f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "#https://pixabay.com/photos/portrait-woman-model-face-6054910/\n",
    "input_image = Image.open(\"portrait.png\")\n",
    "input_image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "66463f61-6dde-41d1-a77e-36280964c9be",
   "metadata": {
    "scrolled": true,
    "tags": []
   },
   "outputs": [],
   "source": [
    "gen_latents = inversestablediffusion(input_image, \"a photo of a woman with blonde hair\", refine_iterations=5, guidance_scale=4.0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2d29ee30-6f45-4bfb-9626-473460869b19",
   "metadata": {},
   "outputs": [],
   "source": [
    "stablediffusion(\"a photo of a woman with blonde hair\", guidance_scale=4.0, init_latents=gen_latents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b74f85fb-c13c-4467-80e5-abe5a832938b",
   "metadata": {},
   "outputs": [],
   "source": [
    "stablediffusion(\"a photo of a woman with blonde hair\", \"a photo of a young man with short blonde hair\", prompt_edit_spatial_end=0.9, guidance_scale=4.0, init_latents=gen_latents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c811556f-9707-44d1-b136-d190bdfdecba",
   "metadata": {},
   "outputs": [],
   "source": [
    "stablediffusion(\"a photo of a woman with blonde hair\", \"a photo of a young man with long blonde hair and glasses\", guidance_scale=4.0, init_latents=gen_latents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ab4f5614-1327-40f7-ba96-9cef764f39e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "stablediffusion(\"a photo of a woman with blonde hair\", \"a photo of a woman with blonde hair, tanned skin\", prompt_edit_spatial_end=0.9, guidance_scale=4.0, init_latents=gen_latents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d48a32e2-d32b-4cd7-a277-2c133f981100",
   "metadata": {},
   "outputs": [],
   "source": [
    "stablediffusion(\"a photo of a woman with blonde hair\", \"a photo of a young woman with short blonde hair and earrings, smiling\", prompt_edit_token_weights=[(14, -2.5)], prompt_edit_spatial_end=0.9, guidance_scale=4.0, init_latents=gen_latents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e9779806-212a-4b21-abd7-1057b029af23",
   "metadata": {},
   "outputs": [],
   "source": [
    "stablediffusion(\"a photo of a woman with blonde hair\", \"a photo of a young woman with brown hair, smiling\", prompt_edit_token_weights=[(11, -2.5)], guidance_scale=4.0, init_latents=gen_latents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eee76816-9192-4c9e-b547-1fcdea42a719",
   "metadata": {},
   "outputs": [],
   "source": [
    "stablediffusion(\"a photo of a woman with blonde hair\", \"a photo of a young woman with wavy black hair, smiling, raised eyebrows\", prompt_edit_token_weights=[(12, -3), (14, -3)], guidance_scale=4.0, init_latents=gen_latents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "050427ce-d8a1-493d-bc9c-da4b4c882ed4",
   "metadata": {},
   "outputs": [],
   "source": [
    "stablediffusion(\"a photo of a woman with blonde hair\", \"a photo of a young woman with curly black hair, smiling, raised eyebrows\", prompt_edit_token_weights=[(12, -2.5), (14, -3)], init_latents=gen_latents, guidance_scale=4.0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7918056e-a5b4-40ed-9b49-511b00caa5b8",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "diffusers",
   "language": "python",
   "name": "diffusers"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
